package dockerd

import (
	_ "embed"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/registry"
	"github.com/pkg/errors"
	"github.com/shirou/gopsutil/v3/host"

	"gopkg.openfuyao.cn/bkeadm/pkg/executor/exec"
	"gopkg.openfuyao.cn/bkeadm/pkg/global"
	"gopkg.openfuyao.cn/bkeadm/utils"
	"gopkg.openfuyao.cn/bkeadm/utils/log"
)

var (
	//go:embed tlscert.sh
	tlsCertScript string
)

var defaultConfigFile = "/etc/docker/daemon.json"

type configMirror struct {
	ExecOptions  []string `json:"exec-opts,omitempty"`
	GraphDriver  string   `json:"storage-driver,omitempty"`
	GraphOptions []string `json:"storage-opts,omitempty"`
	DataRoot     string   `json:"data-root,omitempty"`
	LogConfig
	registry.ServiceOptions
	CommonUnixConfig
}

// LogConfig represents the default log configuration.
// It includes json tags to deserialize configuration from a file
// using the same names that the flags in the command line use.
type LogConfig struct {
	Type   string            `json:"log-driver,omitempty"`
	Config map[string]string `json:"log-opts,omitempty"`
}

// CommonUnixConfig defines configuration of a docker daemon that is
// common across Unix platforms.
type CommonUnixConfig struct {
	Runtimes          map[string]container.HostConfig `json:"runtimes,omitempty"`
	DefaultRuntime    string                          `json:"default-runtime,omitempty"`
	DefaultInitBinary string                          `json:"default-init,omitempty"`
}

func initConfig() *configMirror {
	return &configMirror{
		ExecOptions: []string{"native.cgroupdriver=systemd"},
		GraphDriver: "overlay2",
		//GraphOptions: []string{"overlay2.override_kernel_check=true"},
		LogConfig: LogConfig{
			Type: "json-file",
			Config: map[string]string{
				"max-size": "100m",
			},
		},
		ServiceOptions:   registry.ServiceOptions{},
		CommonUnixConfig: CommonUnixConfig{},
	}
}

func initDockerConfig(domain string, runtimeStorage string) (bool, error) {
	if !utils.Exists(runtimeStorage) {
		err := os.MkdirAll(runtimeStorage, 0644)
		if err != nil {
			return false, err
		}
	}
	if !utils.Exists(defaultConfigFile) {
		mirror := initConfig()
		mirror.InsecureRegistries = append(mirror.InsecureRegistries, domain)
		mirror.DataRoot = runtimeStorage
		h, _ := host.Info()
		if (strings.ToLower(h.Platform) == "centos" && strings.HasPrefix(h.PlatformVersion, "8")) ||
			strings.ToLower(h.Platform) == "kylin" {
			mirror.GraphDriver = "overlay2"
			mirror.GraphOptions = []string{}
		}
		b, err := json.MarshalIndent(mirror, "", " ")
		if err != nil {
			return false, err
		}
		err = os.WriteFile(defaultConfigFile, b, 0644)
		if err != nil {
			return false, err
		}
		return true, nil
	}
	restartFlag := false
	conf := make(map[string]interface{})
	fs, err := os.ReadFile(defaultConfigFile)
	if err != nil {
		return false, err
	}
	err = json.Unmarshal(fs, &conf)
	if err != nil {
		return false, err
	}

	ir := []string{}
	if v, ok := conf["insecure-registries"]; ok {
		aInterface := v.([]interface{})
		for _, v1 := range aInterface {
			ir = append(ir, v1.(string))
		}
	}
	if !utils.ContainsString(ir, domain) {
		ir = append(ir, domain)
		restartFlag = true
	}
	conf["insecure-registries"] = ir

	eo := []string{}
	if v, ok := conf["exec-opts"]; ok {
		bInterface := v.([]interface{})
		for _, v1 := range bInterface {
			eo = append(eo, v1.(string))
		}
	}
	if !utils.ContainsString(eo, "native.cgroupdriver=systemd") {
		eo = append(eo, "native.cgroupdriver=systemd")
		restartFlag = true
	}
	conf["exec-opts"] = eo

	dataRoot := conf["data-root"]
	if dataRoot.(string) != runtimeStorage {
		conf["data-root"] = runtimeStorage
		restartFlag = true
	}

	b, err := json.MarshalIndent(conf, "", " ")
	if err != nil {
		return false, err
	}
	err = os.WriteFile(defaultConfigFile, b, 0644)
	if err != nil {
		return false, err
	}
	return restartFlag, nil
}

func ensureRuncVersion() bool {
	output, err := global.Command.ExecuteCommandWithOutput("sudo", "runc", "-v")
	if err != nil {
		return false
	}
	runcVersion := strings.Split(output, "\n")
	if len(runcVersion) == 0 {
		return false
	}
	vs := strings.Split(runcVersion[0], " ")
	// 比较字符串
	if len(vs[len(vs)-1]) > 6 && vs[len(vs)-1] >= "1.1.12" {
		return false
	} else {
		runc := filepath.Join(global.Workspace, "mount", "source_registry", "files", "runc-"+runtime.GOARCH)
		// 将runc拷贝到/usr/bin/runc
		err = utils.CopyFile(runc, "/usr/bin/runc")
		if err != nil {
			log.BKEFormat(log.ERROR, fmt.Sprintf("copy runc %s to /usr/bin/runc failed %s", runc, err.Error()))
			return true
		}
		_ = global.Command.ExecuteCommand("sudo", "chmod", "+x", "/usr/bin/runc")
		return true
	}
}

func configDockerTls(tlsHost string) error {
	log.BKEFormat(log.INFO, fmt.Sprintf("config docker tls, tls host: %s", tlsHost))
	if tlsHost == "" {
		tlsHost = "127.0.0.1"
	}
	var cfg interface{}
	// save tlscert.sh to /etc/docker/certs/tlscert.sh
	if !utils.Exists("/etc/docker/certs") {
		err := os.MkdirAll("/etc/docker/certs", 0755)
		if err != nil {
			return errors.Wrapf(err, "create docker certs dir failed")
		}
	}
	err := os.WriteFile("/etc/docker/certs/tlscert.sh", []byte(tlsCertScript), 0755)
	if err != nil {
		return errors.Wrapf(err, "write tlscert.sh failed")
	}

	executor := &exec.CommandExecutor{}
	output, err := executor.ExecuteCommandWithCombinedOutput("/bin/sh", "-c", "cd /etc/docker/certs && ./tlscert.sh "+tlsHost)
	if err != nil {
		return errors.Wrapf(err, "generate docker tls cert failed, output: %s, err: %v", output, err)
	}

	// export DOCKER_CONFIG=/etc/docker/certs to /etc/profile
	output, err = executor.ExecuteCommandWithCombinedOutput("/bin/sh", "-c", "echo 'export DOCKER_CONFIG=/etc/docker/certs' >> /etc/profile")
	if err != nil {
		log.Warnf("export DOCKER_CONFIG=/etc/docker/certs to /etc/profile failed, output: %s, err: %v", output, err)
	}
	log.Debugf("export DOCKER_CONFIG=/etc/docker/certs to /etc/profile, output: %s", output)
	// source /etc/profile use bash
	output, err = executor.ExecuteCommandWithCombinedOutput("/bin/bash", "-c", "source /etc/profile")
	if err != nil {
		log.Warnf("source /etc/profile failed, output: %s, err: %v", output, err)
	}

	// if daemon.json does not exist, create it,and add tls config
	if !utils.Exists(defaultConfigFile) {
		// create daemon.json
		if !utils.Exists(filepath.Dir(defaultConfigFile)) {
			err := os.MkdirAll(filepath.Dir(defaultConfigFile), 0755)
			if err != nil {
				return errors.Wrapf(err, "create docker daemon config file %s failed", defaultConfigFile)
			}
		}

		log.BKEFormat(log.INFO, fmt.Sprintf("docker daemon config file %s not found, create it", defaultConfigFile))
		_, err := os.OpenFile(defaultConfigFile, os.O_RDONLY|os.O_CREATE, 0644)
		if err != nil {
			return errors.Wrapf(err, "create docker daemon config file %s failed", defaultConfigFile)
		}
		cfg := map[string]interface{}{
			"tls":       true,
			"tlsverify": true,
			"tlscacert": "/etc/docker/certs/ca.pem",
			"tlscert":   "/etc/docker/certs/server-cert.pem",
			"tlskey":    "/etc/docker/certs/server-key.pem",
			"hosts":     []string{"tcp://0.0.0.0:2376", "unix:///var/run/docker.sock"},
		}
		data, err := json.MarshalIndent(cfg, "", " ")
		if err != nil {
			return errors.Wrapf(err, "marshal docker daemon config failed")
		}
		err = os.WriteFile(defaultConfigFile, data, 0644)
		if err != nil {
			return errors.Wrapf(err, "write docker daemon config file %s failed", defaultConfigFile)
		}
		return nil
	}
	// Read the configuration file
	f, err := os.ReadFile(defaultConfigFile)
	if err != nil {
		return errors.Wrapf(err, "read docker daemon config file %s failed", defaultConfigFile)
	}
	if len(f) == 0 {
		cfg = map[string]interface{}{
			"tls":       true,
			"tlsverify": true,
			"tlscacert": "/etc/docker/certs/ca.pem",
			"tlscert":   "/etc/docker/certs/server-cert.pem",
			"tlskey":    "/etc/docker/certs/server-key.pem",
			"hosts":     []string{"tcp://0.0.0.0:2376", "unix:///var/run/docker.sock"},
		}
		data, err := json.MarshalIndent(cfg, "", " ")
		if err != nil {
			return errors.Wrapf(err, "marshal docker daemon config failed")
		}
		err = os.WriteFile(defaultConfigFile, data, 0644)
		if err != nil {
			return errors.Wrapf(err, "write docker daemon config file %s failed", defaultConfigFile)
		}
		return nil
	} else {
		err = json.Unmarshal(f, &cfg)
		if err != nil {
			return errors.Wrapf(err, "unmarshal docker daemon config failed")
		}
	}
	if v, ok := cfg.(map[string]interface{}); ok {
		v["tls"] = true
		v["tlsverify"] = true
		v["tlscacert"] = "/etc/docker/certs/ca.pem"
		v["tlscert"] = "/etc/docker/certs/server-cert.pem"
		v["tlskey"] = "/etc/docker/certs/server-key.pem"

		// add unix:///var/run/docker.sock and tcp://0.0.0.0:2376
		if hosts := v["hosts"]; hosts != nil {
			if h, ok := hosts.([]interface{}); ok {
				for _, host := range h {
					if host == "unix:///var/run/docker.sock" {
						continue
					} else {
						v["hosts"] = append(h, "unix:///var/run/docker.sock")
					}
					if host == "tcp://0.0.0.0:2376" {
						continue
					} else {
						v["hosts"] = append(h, "tcp://0.0.0.0:2376")
					}
				}
			}
		} else {
			v["hosts"] = []interface{}{
				"unix:///var/run/docker.sock",
				"tcp://0.0.0.0:2376",
			}
		}
	}
	data, err := json.MarshalIndent(cfg, "", " ")
	if err != nil {
		return errors.Wrapf(err, "marshal docker daemon config failed")
	}
	err = os.WriteFile(defaultConfigFile, data, 0644)
	if err != nil {
		return errors.Wrapf(err, "write docker daemon config file %s failed", defaultConfigFile)
	}
	return nil
}
